// 7.1.4 构造函数
/**
 * 每个类都分别定义了它的对象被初始化的方式，类通过一个或几个特殊的成员函数来控制其对象的初始化过程，这些函数叫做构造函数（constructor）。
 * 构造函数的任务是初始化类对象的数据成员，无论何时只要类的对象被创建，就会执行构造函数。
 *
 * 构造函数的名字和类名相同。和其他函数不一样的是，构造函数没有返回类型；
 *
 * 不同于其他成员函数，构造函数不能被声明成const的（参见7.1.2节，第231页）。
 * 当我们创建类的一个const对象时，直到构造函数完成初始化过程，对象才能真正取得其“常量”属性。
 * 因此，构造函数在const对象的构造过程中可以向其写值。
 *
 * 对于一个普通的类来说，必须定义它自己的默认构造函数，原因有三：
 * 1. 编译器只有在发现类不包含任何构造函数的情况下才会替我们生成一个默认的构造函数。
 *    一旦我们定义了一些其他的构造函数，那么除非我们再定义一个默认的构造函数，否则类将没有默认构造函数。
 * 2. 对于某些类来说，合成的默认构造函数可能执行错误的操作。
 *    含有内置类型或复合类型成员的类应该在类的内部初始化这些成员，或者定义一个自己的默认构造函数。
 *    否则，用户在创建类的对象时就可能得到未定义的值
 * 3. 有的时候编译器不能为某些类合成默认的构造函数。
 *    例如，如果类中包含一个其他类类型的成员且这个成员的类型没有默认构造函数，那么编译器将无法初始化该成员。
 *    对于这样的类来说，我们必须自定义默认构造函数，否则该类将没有可用的默认构造函数。
 */

#include <iostream>
using std::cerr, std::clog;
using std::cin, std::cout, std::endl;

struct Sales_data
{
    // 新增的构造函数
    Sales_data() = default; // 默认构造函数
    // 和其他函数一样，如果= default在类的内部，则默认构造函数是内联的；如果它在类的外部，则该成员默认情况下不是内联的。
    // 上面的默认构造函数之所以对Sales_data有效，是因为我们为内置类型的数据成员提供了初始值。
    // 如果你的编译器不支持类内初始值，那么你的默认构造函数就应该使用构造函数初始值列表（马上就会介绍）来初始化类的每个成员。
    Sales_data(const std::string &s) : bookNo(s) {} // 构造函数初始值列表指的是，构造函数参数列表冒号后的及函数体左花括号前的那一部分
    Sales_data(const std::string &s, unsigned n, double p) : bookNo(s), units_sold(n), revenue(p * n) {}
    Sales_data(std::istream &);

    // 新成员，关于Sales_data对象的操作
    // 定义在类内部的函数是隐式的inline函数
    std::string isbn() const { return bookNo; } // const成员函数
    // std::string isbn() const { return this->bookNo; } // 同上，但没有必要，因为this是隐式的
    /**
     * 可把isbn常量成员函数想象成如下形式（将this定义为指向常量的常量指针）：
     * std::string Sales_data::isbn(const Sales_data *const this) { return this->bookNo; }
     *
     * 常量对象，以及常量对象的引用或指针都只能调用常量成员函数。
     */
    Sales_data &combine(const Sales_data &);
    double avg_price() const;

    // 数据成员和2.6.1节相比没有改变
    std::string bookNo;
    unsigned units_sold = 0; // unsigned是unsigned int的缩写
    double revenue = 0.0;
}; // 类必须以分号;结尾

// Sales_data的非成员接口函数
Sales_data add(const Sales_data &, const Sales_data &);
std::ostream &print(std::ostream &, const Sales_data &);
std::istream &read(std::istream &, Sales_data &);

// 类外部定义的成员的名字必须包含它所属的类名
double Sales_data::avg_price() const
{
    if (units_sold)
        return revenue / units_sold;
    else
        return 0;
}

// 定义一个返回this对象的函数
Sales_data &Sales_data::combine(const Sales_data &rhs)
{
    units_sold += rhs.units_sold; // 把rhs的成员加到this对象的成员上
    revenue += rhs.revenue;
    return *this; // 返回调用该函数的对象，记住this是个常量指针，不要直接返回this，要返回this指向的对象
}

// 定义read和print非成员函数
std::istream &read(std::istream &is, Sales_data &item)
{
    double price = 0;
    is >> item.bookNo >> item.units_sold >> price;
    item.revenue = price * item.units_sold;
    return is;
}
std::ostream &print(std::ostream &os, const Sales_data &item)
{
    os << item.isbn() << " " << item.units_sold << " " << item.revenue << " " << item.avg_price();
    return os;
}

// 定义add非成员函数
Sales_data add(const Sales_data &lhs, const Sales_data &rhs)
{
    Sales_data sum = lhs;
    sum.combine(rhs);
    return sum;
}

// 含有内置类型或复合类型成员的类应该在类的内部初始化这些成员，或者定义一个自己的默认构造函数。
// 否则，用户在创建类的对象时就可能得到未定义的值
struct TestClass
{
    std::string a;
    int i;
    double b;
    unsigned c;
};

// 在类的外部定义构造函数
Sales_data::Sales_data(std::istream &is)
{
    read(is,*this); // 从is中读取一条交易记录然后存入this对象中
}

int main()
{
    TestClass tc;
    cout << tc.a << " " << tc.i << " " << tc.b << " " << tc.c << " " << endl;

    Sales_data total;     // 保存当前求和结果的变量
    if (read(cin, total)) // 读入第一笔交易
    {
        Sales_data trans;        // 保存下一条交易数据的变量
        while (read(cin, trans)) // 读入剩余的交易数据
        {
            if (total.isbn() == trans.isbn()) // 检查isbn
            {
                total.combine(trans); // 更新变量total当前的值
            }
            else
            {
                print(cout, total) << endl; // 输出结果
                total = trans;              // 处理下一本书
            }
        }
        print(cout, total) << endl; // 输出最后一条交易
    }
    else
    {
        cerr << "No data?!" << endl; // 没有输入任何信息，通知用户
    }

    return 0;
}